fix(spec): collapse hook event taxonomy 18 → 8; findOne fires find hooks#3206
Merged
Merged
Conversation
…oks (#3195) The HookEvent enum declared 18 events but the engine only ever dispatches 8. The 10 dead ones (beforeFindOne/afterFindOne, beforeCount/afterCount, beforeAggregate/afterAggregate, beforeUpdateMany/afterUpdateMany, beforeDeleteMany/afterDeleteMany) mirrored the engine method table rather than domain events, so a hook subscribing to them registered fine and silently never fired — the same declared≠enforced failure mode as the validation delete-event gap (#3184), and the skills even shipped a copy-pasteable afterFindOne masking example that no-ops. Design decision (see issue): collapse the taxonomy, don't build out dispatch. Peer platforms (Salesforce, Postgres, Rails) don't expose per-method read triggers or single/bulk splits — reads are one materialization event, writes are bulk-first. So: - findOne now fires the SAME beforeFind/afterFind hooks as find (the read event attaches to record materialization, not the method) — one subscription covers every read shape. - Bulk (multi:true) writes already fire the singular before/after write events with the row-scoping predicate in ctx.input.ast; documented, no *Many event. - Removed the 10 dead enum values. Read authz/row-filtering is the RLS/ permission layer; field masking is field metadata — not per-author hooks. - engine.registerHook warns when a hook subscribes to a non-dispatched event, so enum-vs-dispatch drift can't recur silently. No shipped hook or authored metadata used any removed event. Skills and docs (hooks.md, data-hooks.md, kernel/events.mdx, api/data-flow.mdx, protocol/objectql/schema.mdx) updated to teach 8 events + the declarative alternatives; regenerated content/docs/references/data/hook.mdx. Added tests for findOne firing find hooks and the registration guard. Closes #3195 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VCUSMJBsX14C3RQdWFw7N7
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Contributor
📓 Docs Drift CheckThis PR changes 2 package(s): 107 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
os-zhuang
marked this pull request as ready for review
July 18, 2026 11:38
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes #3195. The
HookEventenum declared 18 events but the engine only ever dispatches 8. The 10 dead ones (beforeFindOne/afterFindOne,beforeCount/afterCount,beforeAggregate/afterAggregate,beforeUpdateMany/afterUpdateMany,beforeDeleteMany/afterDeleteMany) mirrored the engine method table rather than domain events, so a hook subscribing to them registered fine and then silently never fired — the samedeclared ≠ enforcedfailure mode as the validationevents: ['delete']gap (#3184). The skills even shipped a copy-pasteableafterFindOnemasking example that no-ops.Design decision (from the issue's review): collapse the taxonomy, don't build out dispatch. Peer platforms don't expose per-method read triggers or a single/bulk split — Salesforce/Postgres/Rails treat reads as one materialization event and writes as bulk-first. The 18-value enum copied Mongoose's per-method shape (its most notorious footgun); the dead events' jobs already have correct homes (read authz → RLS/permissions, masking → field metadata, bulk → the singular write events).
Changes
packages/spec/src/data/hook.zod.ts—HookEventnarrowed to 8 (beforeFind/afterFind+ insert/update/delete pairs);HookContext.inputdoc formalizes that bulkmulti:truewrites fire the singular events with the predicate ininput.ast.packages/objectql/src/engine.ts—findOnenow firesbeforeFind/afterFind(mirrorsfind(); one read event covers both).registerHookwarns when a hook subscribes to a non-dispatched event (aDISPATCHABLE_HOOK_EVENTSset is the single source of truth, kept in lockstep with thetriggerHookscall sites).objectstack-data/rules/hooks.md,references/data-hooks.md) — teach 8 events + a responsibility table; fixed the double-subscribe['afterFind','afterFindOne']masking example to a singleafterFindand pointed static masking at field metadata.kernel/events.mdx,api/data-flow.mdx,protocol/objectql/schema.mdx) — corrected; regeneratedcontent/docs/references/data/hook.mdx..changeset/— patch (spec + objectql).Behavior change
findOnenow runsbeforeFind/afterFindhooks (previously it ran none). Fail-closed direction: a read hook that authors expected to fire on single-record reads now does. No existing hook is negatively affected.Tests
engine.test.ts—findOnefiresbeforeFind+afterFindin order; anafterFindhook can transform thefindOneresult;registerHookwarns for a non-dispatched event and stays quiet for a dispatchable one.hook.test.ts— enum accepts the 8, rejects all 10 removed values.Verified: spec 253 files / 6774 tests green, objectql 71 files / 944 tests green,
check:docs253 files in sync, spec+objectql builds green, eslint clean. No source consumer references the removed event names.Lineage
Third fix in the PD #10 events-enum audit line: #3160 (validation on multi-row updates) → #3189 (trim validation
delete) → this. Sibling issues still open: #3196 (webhookundelete/api), #3197 (schema-only event surfaces).🤖 Generated with Claude Code
https://claude.ai/code/session_01VCUSMJBsX14C3RQdWFw7N7
Generated by Claude Code